<?php
/**
 * Copyright 2011  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * RDF resource
 * 
 * Represents an RDF resource, containing zero or more RDF statements.
 */
class simplerdf_RdfResource extends simplerdf_Rdf
{
	protected $schema = null;
	protected $subject;
	protected $resourceIndex;
	
	/**
	 * Construct a new simplerdf_RdfResource.
	 * Throws a TODO exception if the RDF document provided does not contain any subject.
	 * If the RDF document provided contains multiple resources, only the first resource will be processed.
	 * @param string|array $rdfXmlOrSimpleIndex Either a string containing a RDF/XML document or an ARC2 SimpleIndex
	 * @param RdfDocument $schema RDF Schema (optional)
	 * @param string $base XML base URL (optional)
	 */
	public function __construct($rdfXmlOrSimpleIndex, &$schema = null, $base = '')
	{
		$this->schema =& $schema;
		
		if(!is_array($rdfXmlOrSimpleIndex))
		{
			$parser = ARC2::getRDFXMLParser();
			$parser->parse($base, $rdfXmlOrSimpleIndex);
			//TODO: error handling
			$rdfXmlOrSimpleIndex = $parser->getSimpleIndex();
		}

		// get the first resource
		$keys = array_keys($rdfXmlOrSimpleIndex);
		if(count($keys) > 0)
		{
			$this->subject = $keys[0];
			$this->resourceIndex = $rdfXmlOrSimpleIndex[$this->subject];
		}
		else
		{
			//TODO: throw more specific exception
			throw new Exception("No resource found in RDF document");
		}
	}
	
	/**
	 * Create a new (empty) resource.
	 * @param string $subject The subject of the resource 
	 * @param RdfDocument $schema RDF Schema (optional)
	 * @return simplerdf_RdfResource The new (empty) resource
	 */
	public static function createRdfResource($subject, &$schema = null)
	{
		$index = array(
			$subject => array()
		);
		
		return new simplerdf_RdfResource($index, $schema);
	}
	
	/**
	 * Get the RDF type of the current resource.
	 * @return string The first RDF type URI or null if no type is defined
	 */
	public function getFirstRdfType()
	{
		return $this->getFirstObject('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
	}
	
	/**
	 * Get the RDF types of the current resource.
	 * @return array The RDF types as an array of URI strings
	 */
	public function getRdfTypes()
	{
		return $this->getObjects('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
	}

	/**
	 * Set the RDF type(s) of the current resource, replacing any existing RDF type statements.
	 * @param $rdfType string|array The RDF type(s) as a URI string or an array of URI strings
	 */
	public function setRdfType($rdfType)
	{
		$this->removeStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
		$this->addStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', $rdfType);
	}
	
	/**
	 * Test if the current resource has the RDF type specified.
	 * @param string $rdfType RDF type
	 * @return boolean True if the current resource has the RDF type specified
	 */
	public function hasRdfType($rdfType)
	{
		return $this->hasStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', $rdfType);
	} 
	
	/**
	 * Add a statement with one or more objects.
	 * Duplicate object values are merged.
	 * @param string $predicate The predicate of the new statement
	 * @param string|array $object The object(s) of the new statement as a string or an array of strings
	 */
	public function addStatement($predicate, $object)
	{
		if(is_array($object))
		{
			if(count($object) > 0)
			{
				if(!isset($this->resourceIndex[$predicate]))
				{
					$this->resourceIndex[$predicate] = array();
				}
				
				foreach($object as $objectValue)
				{
					if($objectValue !== null && !in_array($objectValue, $this->resourceIndex[$predicate], true))
					{
						$this->resourceIndex[$predicate][] = $objectValue;
					}
				}
			}
		}
		else
		{
			if($object !== null)
			{
				if(!isset($this->resourceIndex[$predicate]))
				{
					$this->resourceIndex[$predicate] = array();
					$this->resourceIndex[$predicate][] = $object;
				}
				else if(!in_array($object, $this->resourceIndex[$predicate], true))
				{
					$this->resourceIndex[$predicate][] = $object;
				}
			}
		}
	}
	
	/**
	 * Remove statements matching a predicate / object combination.
	 * All statements with the predicate specified will be removed if no object is supplied.
	 * @param string $predicate The predicate to match
	 * @param string|array $object The object(s) to match either as a string or an array of strings (optional)
	 */
	public function removeStatement($predicate, $object = null)
	{
		if($object === null)
		{
			// wipe all statements for the specified predicate
			unset($this->resourceIndex[$predicate]);
		}
		else
		{
			// wipe statements matching the specified predicate and object(s)
			if(isset($this->resourceIndex[$predicate]))
			{
				if(is_array($object))
				{
					foreach($object as $objectValue)
					{
						$pos = array_search($objectValue, $this->resourceIndex[$predicate], true);
						if($pos !== false)
						{
							unset($this->resourceIndex[$predicate][$pos]);
						}
					}
				}
				else
				{
					$pos = array_search($object, $this->resourceIndex[$predicate], true);
					if($pos !== false)
					{
						unset($this->resourceIndex[$predicate][$pos]);
					}
				}
				
				if(count($this->resourceIndex[$predicate]) === 0)
				{
					unset($this->resourceIndex[$predicate]);
				}
			}
		}
	}

	/**
	 * Replace an existing object with a new object.
	 * @param string $predicate The predicate to match
	 * @param string $oldObject The object to match
	 * @param string $newObject The new object
	 * @return boolean True if the replacement operation was successful
	 */
	public function replaceObject($predicate, $oldObject, $newObject)
	{
		$success = false;
		
		// find statement matching the specified predicate and object
		if(isset($this->resourceIndex[$predicate]))
		{
			$pos = array_search($oldObject, $this->resourceIndex[$predicate], true);
			if($pos !== false)
			{
				if($newObject !== null)
				{
					$this->resourceIndex[$predicate][$pos] = $newObject;
				}
				else
				{
					unset($this->resourceIndex[$predicate][$pos]);
				}
				
				$success = true;
			}
		}
		
		return $success;
	}
		
	/**
	 * Gets the subject.
	 * @return string
	 */
	public function getSubject()
	{
		return $this->subject;
	}
	
	/**
	 * Sets the subject.
	 * @param string $subject The subject
	 */
	public function setSubject($subject)
	{
		$this->subject = $subject;
	}
	
	/**
	 * Returns asserted predicates matching for the current subject.
	 * @param string $predicate The predicate to match
	 * @return array Array with predicates as strings
	 */
	public function getPredicates()
	{
		return array_keys($this->resourceIndex);
	}
	
	/**
	 * Returns the first object matching the current subject and specified predicate.
	 * @param string $predicate The predicate to match
	 * @return string Object in string form, returns a null if no objects are found
	 */
	public function getFirstObject($predicate)
	{
		$object = null;
		
		if(isset($this->resourceIndex[$predicate][0]))
		{
			$object = $this->resourceIndex[$predicate][0];
		}
		
		return $object;
	}
	
	/**
	 * Returns objects matching the current subject and specified predicate.
	 * @param string $predicate The predicate to match
	 * @return array List of objects in string form, returns an empty array if no objects are found
	 */
	public function getObjects($predicate)
	{
		$objects = @$this->resourceIndex[$predicate];
		if(isset($objects))
		{
			return $objects;
		}
		else
		{
			return array();
		}
	}

	/**
	 * Check if a statement exists in this resource.
	 * @param String $predicate The predicate to match 
	 * @param String $object The object to match
	 * @return bool True if the statement exists
	 */
	public function hasStatement($predicate, $object)
	{
		$result = false;
		
		if(isset($this->resourceIndex[$predicate]))
		{
			$result = in_array($object, $this->resourceIndex[$predicate]);
		}
		
		return $result;
	}
	
	/**
	 * Merge a RDF resource into this RDF resource.
	 * @param simplerdf_RdfResource $rdfResource RDF resource
	 */
	public function merge($rdfResource)
	{
		$predicates = $rdfResource->getPredicates();
		foreach($predicates as $predicate)
		{
			$this->addStatement($predicate, $rdfResource->getObjects($predicate));
		}
	}
	
	/**
	 * Return the current resource expressed in RDF/XML.
	 * @param bool $skipXmlDeclaration If true, the <?xml version="1.0" encoding="UTF-8"?> section is not returned
	 * @return string The current document expressed in RDF/XML
	 */
	public function getRdfXml($skipXmlDeclaration = false)
	{
		$ser = $this->getRdfXmlSerializer();
		$rdfXml = $ser->getSerializedIndex(array($this->subject => $this->resourceIndex));
		if($skipXmlDeclaration)
		{
			$count = 1;
			$rdfXml = str_replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", "", $rdfXml, $count);
		}
		return $rdfXml;
	}

	/**
	 * Return the current document expressed in RDF/JSON.
	 * @return string The current document expressed in RDF/JSON
	 */
	public function getRdfJson()
	{
		
		$ser = $this->getRdfJsonSerializer();
		$rdfJson = $ser->getSerializedIndex($this->_getSimpleIndex());
		return $rdfJson;
	}
	
	/**
	 * Return the current document expressed as an ARC2 SimpleIndex structure.
	 * @param bool $flattenObjects If true, no object type information is provided
	 * @return array ARC2 SimpleIndex
	 */
	public function _getSimpleIndex($flattenObjects = true)
	{
		if($flattenObjects)
		{
			return array($this->subject => $this->resourceIndex);
		}
		else
		{
			return $this->convertFlatSimpleIndexToSimpleIndex(array($this->subject => $this->resourceIndex));
		}
	}
}